#include "JSINativeModules.h"

#include <string>

#include "DynamicConverter.h"
#include "GenNativeModule.h"
#include "utils/Logger.h"
using namespace facebook::jsi;

namespace jsiexecutor {
extern const char* GEN_NATIVE_MODULE;
extern const char* GLOBAL_THIS;
extern const char* MODULE_NAME;
extern const char* MODULE_ID;
extern const char* METHODS;
extern const char* CONSTANTS;
extern const char* MODULE;

JSINativeModules::JSINativeModules(std::shared_ptr<ModuleRegistry> moduleRegistry)
    : moduleRegistry_(std::move(moduleRegistry)) {}

Value JSINativeModules::getModule(Runtime& rt, const PropNameID& name) {
  if (!moduleRegistry_) {
    logError("module unregistered\n");
    return nullptr;
  }

  std::string moduleName = name.utf8(rt);
  const auto it = objects_.find(moduleName);
  if (it != objects_.end()) {
    return Value(rt, it->second);
  }

  auto module = createModule(rt, moduleName);
  if (!module.has_value()) {
    return nullptr;
  }

  auto result = objects_.emplace(std::move(moduleName), std::move(*module)).first;
  return Value(rt, result->second);
}

void JSINativeModules::reset() {
  objects_.clear();
  genNativeModule_ = std::nullopt;
}

std::optional<Object> JSINativeModules::createModule(Runtime& rt, const std::string& name) {
  if (!genNativeModule_) {
    genNativeModule_ = rt.global().getPropertyAsObject(rt, GLOBAL_THIS).getPropertyAsFunction(rt, GEN_NATIVE_MODULE);
  }

  auto config = moduleRegistry_->getConfig(name);
  auto moduleID = config->moduleID;
  auto methodNames = config->methods;
  Object arg(rt);
  arg.setProperty(rt, MODULE_NAME, String::createFromUtf8(rt, name));
  arg.setProperty(rt, MODULE_ID, Value((int)moduleID));
  Object methods(rt);
  for (const auto& pair : methodNames) {
    methods.setProperty(rt, pair.first.c_str(), Value((int)pair.second));
  }
  arg.setProperty(rt, METHODS, methods);

  if (config->constants.getType() == Dynamic::Type::Object) {
    auto constants = dynamic::dynamicToValue(rt, config->constants);
    arg.setProperty(rt, CONSTANTS, constants);
  }

  Value moduleInfo = genNativeModule_->call(rt, {std::move(arg)});
  std::optional<Object> module(moduleInfo.asObject(rt).getPropertyAsObject(rt, MODULE));

  return module;
}

}  // namespace jsiexecutor
